#include <stdio.h>
#include <string.h>
#include "preq.h"
#include "processor.h"
#include "processor_context.h"
#include "processor_simple.h" 
#include "uarch/stat.h"
#include "uarch/settings.h"
#include "uarch/sim.h"
#include "uarch/types.h"
#if (SESC_EMUL)
#include "ThreadContext.h"
#include "Instruction.h"
#endif

using namespace std;

extern Simulator Sim;

//#undef TICK_TOCK_DEBUG
//#define TICK_TOCK_DEBUG 1

processor::processor (ModuleID moduleID, proc_type_t type)
    : Module (moduleID, "Processor_")
{
    this->moduleID = moduleID;
    this->active_context_list.clear ();
    this->free_context_list.clear ();
    this->suspended_context_list.clear ();
    
    for(int i = 0; i < Sim.settings.threads_per_processor; i++)
    {
        switch (type) {
        case SIMPLE_PROC:
            free_context_list.push_front ((Processor_context *)new Processor_simple (this));
            break;
        case IN_ORDER_PROC:
            break;
        case OOO_PROC:
            break;
        default:
            fatal_error ("Unknown processor type!");
        }
    }

    stats = new Processor_stat_engine (this);
}

processor::~processor ()
{
    delete stats;

    //TODO: sim.cpp should be responsible for this, since processor did not create the thread
    while (!active_context_list.empty ())
    {
        delete active_context_list.front ();
        active_context_list.pop_front ();
    }

    while (!free_context_list.empty ())
    {
        delete free_context_list.front ();
        free_context_list.pop_front ();
    }
}

/** Returns the number of free contexts.  */
int processor::free_contexts ()
{
    return free_context_list.size ();
}

int processor::active_contexts ()
{
    return active_context_list.size ();
}

void processor::assign_thread (Thread *thread)
{
    assert (thread != NULL && "processor.cpp:assign_thread passed NULL");
    assert (free_contexts () > 0 && "processor.cpp:assign_thread assigned thread, but has no free contexts");

    Processor_context *context;
    if (free_contexts () < 1)
        fatal_error ("processor.cpp:assign_thread assigned thread, but has no free contexts");

    context = free_context_list.front();
    free_context_list.pop_front();

    context->thread = thread;
    context->suspended = false;
    assert (context->outstanding_reqs == 0 && context->outstanding_req_list.empty() &&
            "A free context had requests?");

    active_context_list.push_back (context);
}

/** This removes the thread from the context, but does NOT free the context for use
 *  elsewhere.  This is because the outstanding requests need to resolve before the
 *  hardware context resource can be given back to the processor.  It will only free
 *  it if it can safely be freed.  
 */
void processor::remove_thread (Processor_context *context)
{
    assert (context && "processor.cpp::remove_thread () passed NULL Processor_context");
    assert (context->thread != NULL && "processor.cpp:remove_thread () - thread is NULL!");

    active_context_list.remove (context);
    context->suspended = true;

    if (context->context_can_be_released ())
    {
        context->suspended = false;
        free_context_list.push_back (context);
        Sim.emul.update_processor_map (this);
        Sim.emul.schedule ();
    } 
    else
    {
        suspended_context_list.push_back (context);
    }
}

void processor::tick ()
{

    if(moduleID == (ModuleID){63, L1_M}) 
    {
        fprintf(stderr, "proc %lld: ", (uint64_t) Global_Clock);
        if(this->has_waiting_mreq())
            fprintf(stderr, "have one waiting!\n");
        else
            fprintf(stderr, "nope!\n");
    }

    bool se_p;
    LIST<Processor_context *>::iterator context_it;

    /** No active contexts, so return.  */
    if (active_context_list.empty ())
        return;

    /** For each context.  */
    for (context_it = active_context_list.begin(); context_it != active_context_list.end(); context_it++)
        (*context_it)->tick ();

    /** Active context list cleanup for threads that have suspended or exited (se_p == true).  */
    se_p = true;
    while (se_p)
    {
        se_p = false;
        for (context_it = active_context_list.begin(); context_it != active_context_list.end(); context_it++)
        {
            if ((*context_it)->thread->is_suspended () || (*context_it)->thread->is_exited ())
            {
                remove_thread (*context_it);
                se_p = true;
                break;
            }
        }
    }
}

void processor::tock ()
{
    Mreq *request;
    Processor_context *context;

    while ((request = read_input_port ()) != NULL)
    {
        /** Retire in that context */
        context = request->preq->context;
        context->tock (request);

        /** Check if that context was suspended and is now free due to last outstanding.  */
        if (context->context_can_be_released ())
        {
            size_t pre_size;
            pre_size = suspended_context_list.size();
            suspended_context_list.remove (context);
            assert (pre_size != suspended_context_list.size() && "released context was not from suspend?");
            free_context_list.push_back (context);
            Sim.emul.update_processor_map (this);
            Sim.emul.schedule();
        }
        delete request;
    }
}

void processor::dump_contexts ()
{
    int i;
    LIST<Processor_context *>::iterator it;

    i = 0;
    fprintf (stderr, "Active Contexts: %d\n", (int)this->active_context_list.size());
    for (it = active_context_list.begin(); it != active_context_list.end(); it++)
    {
        fprintf (stderr, "Context %d\n", i++);
        (*it)->dump_outstanding_requests();
    }

    fprintf (stderr, "Suspended Contexts: %d\n", (int)this->suspended_context_list.size());
    for (it = suspended_context_list.begin(); it != suspended_context_list.end(); it++)
    {
        fprintf (stderr, "Context %d\n", i++);
        (*it)->dump_outstanding_requests();
    }

    fprintf (stderr, "Free Contexts: %d\n", (int)this->free_context_list.size());
}

Processor_stat_engine::Processor_stat_engine (Module *m)
    : Stat_engine (m),
      insn_count("Instruction count", "" ),
      mem_latency_hist("Processor Request Memory Latency:", "", 100, 10, 0),
      long_mem_latency_hist("Processor Request Memory Latency (long latency):", "", 100, 100, 0),
      L1_miss_mem_latency_hist("Processor Request Memory Latency (L1 miss):", "", 100, 10, 0),
      LSQ_dependence_delay("LSQ Dependence Delay Latency:", "", 100, 10, 0),
      load_count("Load count", ""),
      store_count("Store count", ""),
      ll_count("Load link count", ""),
      sc_count("Store conditional count", ""),
      sync_count("Sync count", "")
{
}

Processor_stat_engine::Processor_stat_engine ()
    : Stat_engine (NULL),
      insn_count("Instruction count", "" ),
      mem_latency_hist("Processor Request Memory Latency:", "", 100, 10, 0),
      long_mem_latency_hist("Processor Request Memory Latency (long latency):", "", 100, 100, 0),
      L1_miss_mem_latency_hist("Processor Request Memory Latency (L1 miss):", "", 100, 10, 0),
      LSQ_dependence_delay("LSQ Dependence Delay Latency:", "", 100, 10, 0),
      load_count("Load count", ""),
      store_count("Store count", ""),
      ll_count("Load link count", ""),
      sc_count("Store conditional count", ""),
      sync_count("Sync count", "")
{
}

Processor_stat_engine::~Processor_stat_engine ()
{
}

void Processor_stat_engine::global_stat_merge(Stat_engine * e)
{
	Processor_stat_engine * global_engine = (Processor_stat_engine*)e;
	global_engine->insn_count += insn_count.get_value ();
	global_engine->mem_latency_hist.merge (&mem_latency_hist);
    global_engine->long_mem_latency_hist.merge (&long_mem_latency_hist);
    global_engine->L1_miss_mem_latency_hist.merge (&L1_miss_mem_latency_hist);
    global_engine->LSQ_dependence_delay.merge (&LSQ_dependence_delay);
    global_engine->load_count += load_count.get_value ();
    global_engine->store_count += store_count.get_value ();
    global_engine->ll_count += ll_count.get_value ();
    global_engine->sc_count += sc_count.get_value ();
    global_engine->sync_count += sync_count.get_value ();
}

void Processor_stat_engine::print_stats (ostream & out)
{
	//if (my_module && my_module->name)
    //out << my_module->name << " OUTPUT" << endl;

	insn_count.print (out);
    mem_latency_hist.print (out);
    long_mem_latency_hist.print (out);
    L1_miss_mem_latency_hist.print (out);
    LSQ_dependence_delay.print (out);
    load_count.print (out);
    store_count.print (out);
    ll_count.print (out);
    sc_count.print (out);
    sync_count.print (out);
}

void Processor_stat_engine::clear_stats()
{
	insn_count.clear ();
	mem_latency_hist.clear ();
    long_mem_latency_hist.clear ();
    L1_miss_mem_latency_hist.clear ();
    LSQ_dependence_delay.clear ();
    load_count.clear ();
    store_count.clear ();
    ll_count.clear ();
    sc_count.clear ();
    sync_count.clear ();
}

void Processor_stat_engine::start_warmup () 
{
}

void Processor_stat_engine::end_warmup () 
{
}

void Processor_stat_engine::save_samples () 
{
}
